// Copyright 2013 Petar Maymounkov
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ripple

import (
	"bufio"
	"fmt"
	"io"
	"path"
	"strings"

	"circuit/kit/rh"
	"circuit/kit/rh/ns"
)

func buildCmdHelp(name string, id uint64) string {
	s := path.Join("/", name)
	return fmt.Sprintf(cmdHelpFmt, s, id, s, s, s)
}

const cmdHelpFmt = `
  This is the command directory for a slab ripple, named "%s", with QID ⟨Q%016x⟩.

  SYNOPSIS

  echo NAME >> chan              # Create a chan ripple called "%s/NAME".
  echo NAME >> mutex             # Create a mutex ripple called "%s/NAME".  
  echo NAME CMD ARG... >> proc   # Run the system command CMD with arguments ARG,
                                 # and create a process ripple called "%s/NAME".
  cat help                       # Read this help screen.

` //

const maxCommandLen = 1e4 // 10K

func NewCmd(s *Slab) rh.FID {
	return ns.NewFID("cmd", &cmdDir{s})
}

//
type cmdDir struct {
	s *Slab
}

func (cd *cmdDir) Help() rh.FID {
	return cd.s.cmdHelpFID
}

func (cd *cmdDir) Chan() rh.FID {
	return NewEndpoint(func(line string) {
		cd.s.MakeChan(strings.TrimSpace(line))
	})
}

func (cd *cmdDir) Mutex() rh.FID {
	return NewEndpoint(func(line string) {
		cd.s.MakeMutex(strings.TrimSpace(line))
	})
}

func (cd *cmdDir) Proc() rh.FID {
	return NewEndpoint(func(line string) {
		parts := strings.Split(line, " ")
		if len(parts) < 2 {
			return
		}
		cd.s.MakeProc(parts[0], parts[1], parts[2:]...)
	})
}

//

func NewEndpoint(eval func(string)) rh.FID {
	return ns.RegulateFork(func() ns.UnregulatedFID {
		fid, r := ns.NewPipeWriterFID("")
		go func() {
			scanner := bufio.NewScanner(io.LimitReader(r, maxCommandLen))
			for scanner.Scan() {
				eval(scanner.Text())
				break
			}
			r.Close()
		}()
		return fid
	})
}

// MakeSlab
func (s *Slab) MakeSlab(name string) (*Slab, rh.FID) {
	s.z.Lock()
	defer s.z.Unlock()
	//
	if !s.approve(name) {
		return nil, nil
	}
	t, fid := NewSlab(name)
	s.add(name, fid)
	return t, fid
}

// MakeChan
func (s *Slab) MakeChan(name string) (*Chan, rh.FID) {
	s.z.Lock()
	defer s.z.Unlock()
	//
	if !s.approve(name) {
		return nil, nil
	}
	c, fid := NewChan(name)
	s.add(name, fid)
	return c, fid
}

// MakeMutex
func (s *Slab) MakeMutex(name string) (*Mutex, rh.FID) {
	s.z.Lock()
	defer s.z.Unlock()
	//
	if !s.approve(name) {
		return nil, nil
	}
	m, fid := NewMutex(name)
	s.add(name, fid)
	return m, fid
}

// MakeProc
func (s *Slab) MakeProc(name string, cmd string, arg ...string) (*Proc, rh.FID, error) {
	s.z.Lock()
	defer s.z.Unlock()
	//
	if !s.approve(name) {
		return nil, nil, rh.ErrExist
	}
	p, fid, err := NewProc(name, cmd, arg...)
	if err != nil {
		s.add(name, NewError(name, err.Error()))
		return nil, nil, err
	}
	s.add(name, fid)
	return p, fid, nil
}

func (s *Slab) approve(name string) bool {
	_, ok := s.z.ripple[name]
	if ok {
		return false
	}
	_, ok = s.cmd[name]
	if ok {
		return false
	}
	return true
}

func (s *Slab) add(name string, r RippleFID) {
	if !s.approve(name) {
		panic("u")
	}
	s.z.ripple[name] = ns.NewFID(name, r)
}
